package com.cms.bs.app.shiro.realm;

import com.cms.bs.app.model.Resource;
import com.cms.bs.app.model.Role;
import com.cms.bs.app.model.User;
import com.cms.bs.config.Constants;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by jiang on 2016-10-27.
 */
public class LoginRealm extends AuthorizingRealm {

    /**
     * 为当前登录的Subject授予角色和权限
     *
     * @本例中该方法的调用时机为需授权资源被访问时
     * @并且每次访问需授权资源时都会执行该方法中的逻辑,这表明本例中默认并未启用AuthorizationCache
     * @若使用了Spring3.1开始提供的ConcurrentMapCache支持,则可灵活决定是否启用AuthorizationCache
     * @比如说这里从数据库获取权限信息时,先去访问Spring3.1提供的缓存,而不使用Shior提供的AuthorizationCache
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取当前登录的用户名,等价于(String)principals.fromRealm(this.getName()).iterator().next()
        String username = (String) super.getAvailablePrincipal(principalCollection);
        List<String> roleList = new ArrayList<String>();
        List<String> permissionList = new ArrayList<String>();
        //从数据库中获取当前登录用户的详细信息
        User user = User.getByName(username);
        if (null != user) {
            //实体类User中包含有用户角色的实体类信息
            if (null != user.getRoles() && user.getRoles().size() > 0) {
                //获取当前登录用户的角色
                for (Role role : user.getRoles()) {
                    roleList.add(role.getName());
                    //实体类Role中包含有角色权限的实体类信息
                    if (null != role.getResources() && role.getResources().size() > 0) {
                        //获取权限
                        for (Resource res : role.getResources()) {
                            if (!StringUtils.isEmpty(res.getPermission())) {
                                permissionList.add(res.getPermission());
                            }
                        }
                    }
                }
            }
        } else {
            throw new AuthorizationException();
        }
        // 为当前用户设置角色和权限
        SimpleAuthorizationInfo simpleAuthorInfo = new SimpleAuthorizationInfo();
        simpleAuthorInfo.addRoles(roleList);
        simpleAuthorInfo.addStringPermissions(permissionList);
        // 若该方法什么都不做直接返回null的话,就会导致任何用户访问/admin/listUser.jsp时都会自动跳转到unauthorizedUrl指定的地址
        return simpleAuthorInfo;
    }

    /**
     * 验证当前登录的Subject
     *
     * @本例中该方法的调用时机为LoginController.login()方法中执行Subject.login()时
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 获取基于用户名和密码的令牌
        // 实际上这个authcToken是从LoginController里面currentUser.login(token)传过来的
        // 两个token的引用都是一样的,本例中是org.apache.shiro.authc.UsernamePasswordToken
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        User user = User.getByName(token.getUsername());
        if (null != user) {
            // 此处无需比对,比对的逻辑Shiro会做,我们只需返回一个和令牌相关的正确的验证信息
            // 第一个参数填登录用户名,第二个参数填合法的登录密码
            AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user.getName(), user.getPassword(), this.getName());
            this.setSession(Constants.CURRENT_USER, user);

            //如果缓存存在权限信息则清空
            Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
            if (cache != null) {
                System.out.println("Attempting to retrieve the AuthorizationInfo from cache.");
                cache.remove(user.getName());
            }

            return authcInfo;
        }
        // 没有返回登录用户名对应的SimpleAuthenticationInfo对象时,就会在LoginController中抛出UnknownAccountException异常
        return null;
    }

    /**
     * 将一些数据放到ShiroSession中,以便于其它地方使用
     *
     * @比如Controller,使用时直接用HttpSession.getAttribute(key)就可以取到
     */
    private void setSession(Object key, Object value) {
        Subject currentUser = SecurityUtils.getSubject();
        if (null != currentUser) {
            Session session = currentUser.getSession();
            System.out.println("Session默认超时时间为[" + session.getTimeout() + "]毫秒");
            if (null != session) {
                session.setAttribute(key, value);
            }
        }
    }

    @Override
    protected void clearCachedAuthorizationInfo(PrincipalCollection principals) {
        super.clearCachedAuthorizationInfo(principals);
    }
}
